#include "MineSweeping.h"

int main()
{
    int size, count;
    VECTOR_2B mine = NULL;
    VECTOR_2U map = NULL;
    bool flag = false;
    initSystem();
    do
    {
        initMap(&mine, &map, &size, &count);
        int correct = gameLoop(mine, map, size, count);
        flag = postHandle(mine, map, size, count, correct);
        clearScreen();
    } while (flag);
    return 0;
}

void clearScreen()
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO buffer;
    GetConsoleScreenBufferInfo(hOut, &buffer);
    COORD coord = {0, 0};
    DWORD count;
    SetConsoleCursorPosition(hOut, coord);
    FillConsoleOutputCharacterA(hOut, ' ', buffer.dwSize.X * buffer.dwSize.Y, coord, &count);
}

void initSystem()
{
    SetConsoleOutputCP(65001);
    setlocale(LC_CTYPE, "zh_CN.UTF-8");
    system("title 扫雷");
    clearScreen();

    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode;
    GetConsoleMode(hIn, &mode);
    mode &= ~ENABLE_QUICK_EDIT_MODE;
    mode &= ~ENABLE_INSERT_MODE;
    mode |= ENABLE_MOUSE_INPUT;
    mode |= ENABLE_EXTENDED_FLAGS;
    SetConsoleMode(hIn, mode);
}

void initMap(VECTOR_2B *mine, VECTOR_2U *map, int *size, int *count)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursor;
    GetConsoleCursorInfo(hOut, &cursor);
    cursor.bVisible = true;
    SetConsoleCursorInfo(hOut, &cursor);

    tag_input:
    printf("请输入雷区大小(最大25):");
    if (scanf("%d", size) != 1)
    {
        while (getchar() != '\n');
        printf("无效的输入！\n");
        system("pause");
        clearScreen();
        goto tag_input;
    }
    *size += 2;

    printf("请输入地雷数量:");
    if (scanf("%d", count) != 1)
    {
        while (getchar() != '\n');
        printf("无效的输入！\n");
        system("pause");
        clearScreen();
        goto tag_input;
    }

    while (getchar() != '\n');
    if (*size <= 2 || *size > 27 || *count <= 0 || *count >= (*size-2) * (*size-2))
    {
        printf("无效的输入！\n");
        system("pause");
        clearScreen();
        goto tag_input;
    }

    *mine = (bool**) malloc(sizeof (bool*) * *size);
    for (int i = 0; i < *size; ++i)
    {
        (*mine)[i] = (bool*) malloc(sizeof (bool) * *size);
        memset((*mine)[i], false, sizeof (bool) * *size);
    }
    *map = (uint8_t**) malloc(sizeof (uint8_t*) * *size);
    for (int i = 0; i < *size; ++i)
    {
        (*map)[i] = (uint8_t*) malloc(sizeof (uint8_t) * *size);
        memset((*map)[i], INDEX_HIDDEN, sizeof (uint8_t) * *size);
    }
    srand(time(NULL));
    clearScreen();
}

void spawnMine(VECTOR_2B mine, int x0, int y0, int size, int count)
{
    int i = 0;
    while (i < count)
    {
        int x = (rand() % (size-2) + 1);
        int y = (rand() % (size-2) + 1);
        if (!mine[x][y] && !(x == x0 && y == y0))
        {
            mine[x][y] = true;
            ++i;
        }
    }
}

void flushUI(VECTOR_2B mine, VECTOR_2U map, int size, int mark, int count)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursor;
    GetConsoleCursorInfo(hOut, &cursor);
    cursor.bVisible = false;
    COORD coord;
    coord.X = 2;
    SetConsoleCursorInfo(hOut, &cursor);
    for (int y = 1; y <= size - 2; ++y)
    {
        coord.Y = y;
        SetConsoleCursorPosition(hOut, coord);
        for (int x = 1; x <= size - 2; ++x)
        {
            printf(mapStrs[map[x][y]]);
        }
    }
    CONSOLE_SCREEN_BUFFER_INFO buffer;
    GetConsoleScreenBufferInfo(hOut, &buffer);
    
    coord.Y += 2;
    SetConsoleCursorPosition(hOut, coord);
    DWORD c;
    FillConsoleOutputCharacter(hOut, L' ', buffer.dwSize.X, coord, &c);
    SetConsoleCursorPosition(hOut, coord);
    printf("%s / %s : %d / %d", mapStrs[INDEX_MARK], mapStrs[INDEX_BOMB], mark, count);
}

Operation handleMouseClick(int size, int *x, int *y)
{
    INPUT_RECORD rcd;
    DWORD rcdnum;
    COORD pos;
    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    while (true)
    {
        ReadConsoleInput(hIn, &rcd, 1, &rcdnum);
        if (rcd.EventType == MOUSE_EVENT)
        {
            pos = rcd.Event.MouseEvent.dwMousePosition;
            pos.X /= 2;
            if (pos.X >= 1 && pos.X <= size - 2 && pos.Y >= 1 && pos.Y <= size - 2)
            {
                *x = pos.X;
                *y = pos.Y;
                switch (rcd.Event.MouseEvent.dwButtonState)
                {
                case FROM_LEFT_1ST_BUTTON_PRESSED:
                    return rcd.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK ? QUICK_SWEEP : OP_SWEEP;
                case RIGHTMOST_BUTTON_PRESSED:
                    return OP_MARK;
                }
            }
        }
    }
}

bool opSweep(VECTOR_2B mine, VECTOR_2U map, int size, int x, int y)
{
    if (x == 0 || y == 0 || x == size - 1 || y == size - 1) return false;
    if (map[x][y] == INDEX_HIDDEN)
    {
        bool flag = false;
        if (!mine[x][y])
        {
            map[x][y] = 0;
            for (int i = 0; i < 8; ++i)
            {
                if (mine[x + modifier[i][0]][y + modifier[i][1]])
                {
                    ++map[x][y];
                }
            }
            if (map[x][y] == 0)
            {
                for (int i = 0; i < 8; ++i)
                {
                    flag |= opSweep(mine, map, size, x + modifier[i][0], y + modifier[i][1]);
                }
                return flag;
            }
        }
        else
        {
            over(mine, map, size);
            return true;
        }
    }
    return false;
}

bool opMark(VECTOR_2B mine, VECTOR_2U map, int size, int x, int y)
{
    if (map[x][y] == INDEX_HIDDEN)
    {
        map[x][y] = INDEX_MARK;
    }
    else if (map[x][y] == INDEX_MARK)
    {
        map[x][y] = INDEX_HIDDEN;
    }
    return false;
}

bool opQuickSweep(VECTOR_2B mine, VECTOR_2U map, int size, int x, int y)
{
    if (map[x][y] == INDEX_HIDDEN)
    {
        return opSweep(mine, map, size, x, y);
    }
    else if (map[x][y] != INDEX_MARK)
    {
        uint8_t sum = 0;
        for (int i = 0; i < 8; ++i)
        {
            if (map[x + modifier[i][0]][y + modifier[i][1]] == INDEX_MARK)
            {
                ++sum;
            }
        }
        if (sum == map[x][y])
        {
            bool flag = false;
            for (int i = 0; i < 8; ++i)
            {
                flag |= opSweep(mine, map, size, x + modifier[i][0], y + modifier[i][1]);
            }
            return flag;
        }
    }
    return false;
}

void over(VECTOR_2B mine, VECTOR_2U map, int size)
{
    for (int i = 0; i < size - 1; ++i)
    {
        for (int j = 0; j < size - 1; ++j)
        {
            if (mine[i][j])
            {
                if (map[i][j] != INDEX_MARK) map[i][j] = INDEX_BOMB;
            }
            else if (map[i][j] == INDEX_HIDDEN)
            {
                map[i][j] = INDEX_VOID;
            }
            else if (map[i][j] == INDEX_MARK)
            {
                map[i][j] = INDEX_WRONG;
            }
        }
    }
}

void paintBoarder(int size)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD coord;
    for (int i = 0; i < size; ++i)
    {
        coord.X = 2 * i;
        coord.Y = 0;
        SetConsoleCursorPosition(hOut, coord);
        printf("🧱");
        coord.Y = size - 1;
        SetConsoleCursorPosition(hOut, coord);
        printf("🧱");
    }
    for (int i = 1; i < size - 1; ++i)
    {
        coord.Y = i;
        coord.X = 0;
        SetConsoleCursorPosition(hOut, coord);
        printf("🧱");
        coord.X = 2 * size - 2;
        SetConsoleCursorPosition(hOut, coord);
        printf("🧱");
    }
}

int gameLoop(VECTOR_2B mine, VECTOR_2U map, int size, int count)
{
    bool isSpawned = false;
    int x, y;
    Operation op;
    paintBoarder(size);
    int correct = 0;
    int mark = 0;
    while (true)
    {
        flushUI(mine, map, size, mark, count);
        op = handleMouseClick(size, &x, &y);
        if (!isSpawned)
        {
            if (op == OP_SWEEP)
            {
                spawnMine(mine, x, y, size, count);
                isSpawned = true;
            }
        }
        if (isSpawned)
        {
            bool flag = operation[op](mine, map, size, x, y) | calculate(mine, map, size, count, &correct, &mark);
            if (flag)
            {
                flushUI(mine, map, size, mark, count);
                return correct;
            }
        }
    }
    return 0;
}

bool calculate(VECTOR_2B mine, VECTOR_2U map, int size, int count, int *correct, int *mark)
{
    *correct = 0;
    *mark = 0;
    for (int i = 1; i <= size - 2; ++i)
    {
        for (int j = 1; j <= size - 2; ++j)
        {
            if (map[i][j] == INDEX_MARK)
            {
                ++(*mark);
                if (mine[i][j])
                {
                    ++(*correct);
                }
            }
        }
    }
    return *correct == count && *mark == count;
}

bool postHandle(VECTOR_2B mine, VECTOR_2U map, int size, int count, int correct)
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD coord = {0, (short)size};
    SetConsoleCursorPosition(hOut, coord);
    grade[correct == count](correct, count);
    printf("按下回车再来一轮？\n");
    clear(mine, map, size);
    Sleep(100);
    return getch() == VK_RETURN;
}

void fail(int correct, int total)
{
    printf("你死了！完成度：%d / %d\n", correct, total);
}

void victory(int correct, int total)
{
    printf("你赢了！完成度: %d / %d\n", correct, total);
}

void clear(VECTOR_2B mine, VECTOR_2U map, int size)
{
    for (int i = 0; i < size; ++i)
    {
        free(mine[i]);
        free(map[i]);
    }
    free(mine);
    free(map);
}